/**
 * Copyright © 2011 Creativor Studio.
 * About license information, please see LICENSE.txt.
 */
package org.creativor.rayson.server;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map.Entry;
import org.creativor.rayson.api.RpcService;
import org.creativor.rayson.api.ServiceRegistration;
import org.creativor.rayson.api.Session;
import org.creativor.rayson.common.ClientSession;
import org.creativor.rayson.common.Invocation;
import org.creativor.rayson.common.RpcCallException;
import org.creativor.rayson.exception.IllegalServiceException;
import org.creativor.rayson.exception.ServiceNotFoundException;
import org.creativor.rayson.exception.UnsupportedVersionException;
import org.creativor.rayson.transport.api.ServiceAlreadyExistedException;
import org.creativor.rayson.transport.server.TransportServer;

// TODO: Auto-generated Javadoc
/**
 * An server that can provide the RPC functions upon the {@link TransportServer}
 * .
 * 
 * @author Nick Zhang
 */
public class RpcServer extends TransportServer implements ServerService
{
	
	private HashMap<String, ServiceReflection> services;
	private WorkerManager workerManager;
	
	/**
	 * Instantiates a new rpc server.
	 */
	public RpcServer()
	{
		super();
		initFields();
	}
	
	/**
	 * Instantiates a new rpc server.
	 * 
	 * @param portNumber
	 *            the port number
	 */
	public RpcServer(int portNumber)
	{
		super(portNumber);
		initFields();
	}
	
	private void initFields()
	{
		services = new HashMap<String, ServiceReflection>();
		this.workerManager = new WorkerManager(this.getConfig().workerCount(), this);
	}
	
	/**
	 * Find.
	 * 
	 * 
	 * @param serviceName
	 *            the service name
	 * @return the service registration
	 * @throws ServiceNotFoundException
	 *             the service not found exception
	 */
	@Override
	public ServiceRegistration find(String serviceName) throws ServiceNotFoundException
	{
		ServiceReflection service = getService(serviceName);
		ServiceDescriptionImpl serviceDescription =
				new ServiceDescriptionImpl(service.getName(), service.getDescription(),
						service.getProtocols());
		return serviceDescription;
		
	}
	
	/**
	 * Gets the server info.
	 * 
	 * @return the server info
	 */
	@Override
	public String getServerInfo()
	{
		return "Rpc server";
	}
	
	private ServiceReflection getService(String serviceName) throws ServiceNotFoundException
	{
		ServiceReflection service = services.get(serviceName);
		if (service == null) throw new ServiceNotFoundException(serviceName + " not found");
		return service;
	}
	
	/**
	 * Invoke call.
	 * 
	 * @param call
	 *            the call
	 */
	protected void invokeCall(ServerCall call)
	{
		if (call.exceptionCatched())
		{
			// no need to invoke.
			return;
		}
		
		Object result;
		Invocation invocation = call.getInvocation();
		ServiceReflection serviceObject;
		
		ClientSession clientSession = call.getClientSession();
		try
		{
			
			serviceObject = getService(clientSession.getServiceName());
			RpcSessionImpl rpcSession =
					new RpcSessionImpl(clientSession, serviceObject.getInstance());
			// Check proxy version first.
			rpcSession.checkProxyVersion(serviceObject.getInstance());
			// update last accessed time of session.
			
			// Set RPC context.
			RpcRequestContext.setSession(rpcSession);
			result = serviceObject.invoke(rpcSession, invocation);
			call.setResult(result);
		} catch (RpcCallException e)
		{
			call.setException(e);
		} catch (ServiceNotFoundException e)
		{
			call.setException(new RpcCallException(e));
		} catch (UnsupportedVersionException e)
		{
			call.setException(new RpcCallException(e));
		} finally
		{
			RpcRequestContext.abandonSession();
		}
	}
	
	/**
	 * List.
	 * 
	 * @return the service registration[]
	 */
	@Override
	public ServiceRegistration[] list()
	{
		List<ServiceRegistration> list = new ArrayList<ServiceRegistration>();
		
		for (Entry<String, ServiceReflection> entry : services.entrySet())
		{
			
			ServiceReflection serviceReflection = entry.getValue();
			list.add(new ServiceDescriptionImpl(serviceReflection.getName(), serviceReflection
					.getDescription(), serviceReflection.getProtocols()));
		}
		
		return list.toArray(new ServiceDescriptionImpl[0]);
	}
	
	/**
	 * Return true if current thread is in handling the RPC request on the
	 * server side.
	 */
	public static boolean inHandlingRequest()
	{
		return (Thread.currentThread() instanceof CallWorker);
	}
	
	/**
	 * Register service.
	 * 
	 * @param serviceName
	 *            the service name
	 * @param description
	 *            the description
	 * @param serviceInstance
	 *            the service instance
	 * @throws ServiceAlreadyExistedException
	 *             the service already existed exception
	 * @throws IllegalServiceException
	 *             the illegal service exception
	 */
	public void registerService(String serviceName, String description, RpcService serviceInstance)
			throws ServiceAlreadyExistedException, IllegalServiceException
	{
		synchronized (services)
		{
			if (services.containsKey(serviceName))
				throw new ServiceAlreadyExistedException(serviceName);
			ServiceReflection service =
					new ServiceReflection(serviceName, description, serviceInstance);
			services.put(serviceName, service);
		}
	}
	
	/**
	 * Start.
	 * 
	 * @throws IOException
	 *             Signals that an I/O exception has occurred.
	 */
	@Override
	public void start() throws IOException
	{
		
		// 1. start supper transport server first.
		super.start();
		
		// 2. start call workers.
		this.workerManager.start();
		
		// 3. Register it self as a service.
		try
		{
			this.registerService(ServerService.NAME, ServerService.DESCRIPTION, this);
		} catch (Exception e)
		{
			throw new RuntimeException(e);
		}
	}
	
	/**
	 * Stop.
	 * 
	 * @throws IOException
	 *             Signals that an I/O exception has occurred.
	 */
	@Override
	public void stop() throws IOException
	{
		// 1. quit worker manager.
		this.workerManager.quit();
		// 2. stop supper transport server.
		super.stop();
	}
	
	/**
	 * Checks if is supported version.
	 * 
	 * @param session
	 *            the session
	 * @return true, if is supported version
	 */
	@Override
	public boolean isSupportedVersion(Session session)
	{
		return true;
	}
}
